package cn.itsource.ymcc.service.impl;

import cn.itsource.ymcc.client.CourseFeignClient;
import cn.itsource.ymcc.constants.BaseConstants;
import cn.itsource.ymcc.context.LoginContext;
import cn.itsource.ymcc.domain.*;
import cn.itsource.ymcc.dto.CourseInfoDto;
import cn.itsource.ymcc.dto.KillPlaceOrderDto;
import cn.itsource.ymcc.dto.PlaceOrderDto;
import cn.itsource.ymcc.dto.StationMessageSend2MQ;
import cn.itsource.ymcc.exception.GlobleException;
import cn.itsource.ymcc.mapper.CourseOrderItemMapper;
import cn.itsource.ymcc.mapper.CourseOrderMapper;
import cn.itsource.ymcc.query.CourseOrderQuery;
import cn.itsource.ymcc.result.JSONResult;
import cn.itsource.ymcc.result.PageList;
import cn.itsource.ymcc.service.ICourseOrderService;
import cn.itsource.ymcc.to.KilledInfo;
import cn.itsource.ymcc.to.PlaceCourseOrderItemTo;
import cn.itsource.ymcc.to.PlaceCourseOrderTo;
import cn.itsource.ymcc.util.AssertUtil;
import cn.itsource.ymcc.util.StrUtils;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.*;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author whale
 * @since 2021-09-23
 */
@Service
@Transactional
@Slf4j
public class CourseOrderServiceImpl extends ServiceImpl<CourseOrderMapper, CourseOrder> implements ICourseOrderService {

    @Autowired
    private CourseFeignClient courseFeignClient;

    @Autowired
    private CourseOrderItemMapper courseOrderItemMapper;

    @Autowired
    private CourseOrderMapper courseOrderMapper;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;

    @Autowired
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 秒杀下单
     */
    @Override
    public JSONResult killPlaceOrder(KillPlaceOrderDto dto) {
        AssertUtil.isNotEmpty(dto.getOrderNo(),"非法请求[订单号不可空]");
        Long userId = LoginContext.getLogin().getId();

        //获取秒杀记录
        KilledInfo killedInfo = (KilledInfo) redisTemplate.opsForValue().get(BaseConstants.Kill.KEY_KILLED_INFO + dto.getOrderNo() + ":" + userId);
        AssertUtil.isNotNull(killedInfo,"非法请求[秒杀记录不存在]");
        //保存订单

        PlaceOrderDto placeOrderDto = new PlaceOrderDto();
        //真实课程ID
        placeOrderDto.setCourseIds(Arrays.asList(killedInfo.getCourseId()));
        placeOrderDto.setPayType(dto.getPayType());
        placeOrderDto.setToken(dto.getToken());
        //1代表秒杀
        placeOrderDto.setType(1);
        JSONResult jsonResult = placeOrder(placeOrderDto, killedInfo);
        //下单成功，删除秒杀资格
        redisTemplate.delete(BaseConstants.Kill.KEY_KILLED_INFO + dto.getOrderNo() + ":" + userId);
        return jsonResult;
    }

    @Override
    public String createTokenForOrder() {
        //生成令牌
        String token = StrUtils.getComplexRandomString(16)+LoginContext.getLogin().getId();
        redisTemplate.opsForValue().set(token,"1");
        return token;
    }

    /**
     * 异步下单，处理高并发
     * 同一个课程可以多次下单，但是不能冲突提交表单
     */
    @Override
    public JSONResult placeOrder(PlaceOrderDto dto) {
        return placeOrder(dto,null);
    }
    public JSONResult placeOrder(PlaceOrderDto dto,KilledInfo killedInfo ) {

        AssertUtil.isNotNull(dto.getCourseIds(),"无效的课程ID");

        //秒杀下单的话，只能有1个课程
        if(killedInfo != null)AssertUtil.isTrue(dto.getCourseIds().size() == 1,"非法请求[秒杀课程只能有1种]");

        AssertUtil.isNotEmpty(dto.getToken(),"非法请求");

        //Token判断重复提交
        boolean hasKey = redisTemplate.hasKey(dto.getToken());
        AssertUtil.isTrue(hasKey,"表单重复提交，请重新发起下单请求");

        //构建课程订单数据
        //远程调用课程Feign查询课程
        JSONResult jsonResult = courseFeignClient.getCourseInfos(dto.getCourseIds());
        AssertUtil.isTrue(jsonResult.isSuccess(),"课程查询失败");
        AssertUtil.isNotNull(jsonResult.getData(),"课程查询失败");
        List<CourseInfoDto> courseInfoDtos = JSON.parseArray(jsonResult.getData().toString(), CourseInfoDto.class);

        //生成唯一单号
        String orderNo = StrUtils.getRandomStringForTime()+StrUtils.getRandomString(6);

        List<PlaceCourseOrderItemTo> itemTos = new ArrayList<>(dto.getCourseIds().size());

        StringBuilder courseNames = new StringBuilder();

        //处理课程
        BigDecimal totalPrice = new BigDecimal("0.00");
        for (CourseInfoDto courseInfoDto : courseInfoDtos) {
            Course course = courseInfoDto.getCourse();
            CourseMarket courseMarket = courseInfoDto.getCourseMarket();

            BigDecimal price = courseMarket.getPrice();
            if(killedInfo != null){
                //如果是秒杀，支付金额以秒杀金额为准，秒杀的话课程只有1个
                price = killedInfo.getAmount();
            }

            itemTos.add(new PlaceCourseOrderItemTo(course.getId(),course.getName(),course.getPic(),price));
            courseNames.append(course.getName()).append(";");
            //普通下单
            totalPrice = totalPrice.add(price);
        }

        //下单需要的数据

        PlaceCourseOrderTo placeCourseOrderTo = new PlaceCourseOrderTo(
                totalPrice,
                dto.getPayType(),
                null,
                "userId="+LoginContext.getLogin().getId()+"&relationId="+ StringUtils.join(dto.getCourseIds(),","),
                orderNo,
                LoginContext.getLogin().getId(),
                "购买课程 ["+courseNames.toString()+"] , 支付["+totalPrice+"] 元",
                itemTos,killedInfo);


        //保存支付单消息=========================================================================================================

        //该参数用来判断是否可用下单，防止课程重复下单
        Map<String,String> args = new HashMap<>();
        args.put("SaveOrder","true");

        //构建消息
        Message payOrderMessage =  MessageBuilder.withPayload(JSON.toJSONString(placeCourseOrderTo)).build();
        //发送下单的事务消息
        TransactionSendResult transactionSendResultForPayOrder = rocketMQTemplate.sendMessageInTransaction(
                BaseConstants.OrderMQ.TX_ORDER_GROUP,
                BaseConstants.OrderMQ.TOPIC_COURSE_ORDER +":"+BaseConstants.OrderMQ.TAGS_PAY_ORDER,
                payOrderMessage, args);

        if(transactionSendResultForPayOrder.getSendStatus() != SendStatus.SEND_OK){
            throw new GlobleException("下单失败");
        }

        //推送一个站内信消息=========================================================================================================

        Message<String> stationMessage = MessageBuilder.withPayload(
                JSON.toJSONString(new StationMessageSend2MQ("下单成功", "课程下单成功,请在30分钟内支付", Arrays.asList(LoginContext.getLogin().getId()), "订单消息"))).build();
        args.put("SaveOrder","false");

        TransactionSendResult transactionSendResultForMessage = rocketMQTemplate.sendMessageInTransaction(
                BaseConstants.OrderMQ.TX_ORDER_GROUP,
                BaseConstants.MessageMQ.TOPIC_MESSAGE +":"+ BaseConstants.MessageMQ.TAGS_STATION_MESSAGE,
                stationMessage, args);
        if(transactionSendResultForMessage.getSendStatus() != SendStatus.SEND_OK){
            throw new GlobleException("下单失败");
        }


        //发送一个延迟消息到MQ===========================================================================================================

        SendResult result = rocketMQTemplate.syncSend(
                //topic:tags
                BaseConstants.OrderMQ.TOPIC_COURSE_ORDER_DEALY + ":" + BaseConstants.OrderMQ.TAGS_COURSE_ORDER_DEALY,
                //消息延迟级别5，1分钟延迟
                payOrderMessage, 2000, 6);
        log.info("发送延迟消息，{}",payOrderMessage);
        if(result.getSendStatus() != SendStatus.SEND_OK){
            //延迟消息发送失败，保存记录到DB，做定时重试或人工处理
            log.info("延迟消息发送失败 {}，",payOrderMessage);
        }

        //删除防重令牌
        redisTemplate.delete(dto.getToken());

        return JSONResult.success(orderNo,"200");
    }

        /**
         * 异步下单，处理高并发
         */
    public JSONResult saveOrder(PlaceCourseOrderTo to) {

        List<PlaceCourseOrderItemTo> itemTos = to.getItemTos();

        //创建订单
        CourseOrder order = new CourseOrder();
        order.setCreateTime(new Date());
        order.setTotalCount(itemTos.size());
        order.setTitle(to.getSubject());
        order.setPayType(to.getPayType());
        order.setStatusOrder(BaseConstants.Order.STATUS_APPLY);
        //根据时间生成订单号,再加上6位随机字符串
        order.setOrderNo(to.getOrderNo());
        order.setTotalAmount(to.getAmount());

        //登录用户
        order.setUserId(to.getUserId());
        baseMapper.insert(order);

        itemTos.forEach(itemTo->{
            //创建订单明细
            CourseOrderItem item = new CourseOrderItem();
            item.setAmount(itemTo.getPrice());
            item.setCount(1);
            item.setCourseId(itemTo.getCourseId());
            item.setCourseName(itemTo.getCourseName());
            item.setCoursePic(itemTo.getCoursePic());
            item.setCreateTime(order.getCreateTime());
            item.setOrderId(order.getId());
            item.setOrderNo(order.getOrderNo());
            courseOrderItemMapper.insert(item);
        });
        return JSONResult.success(order.getOrderNo());
    }

    @Override
    public CourseOrder selectByOrderNo(String orderNo) {
        CourseOrder courseOrder = courseOrderMapper.selectByOrderNo(orderNo);
        if(null != courseOrder){
            Wrapper<CourseOrderItem> wrapper = new EntityWrapper<>();
            wrapper.eq("order_no",orderNo);
            List<CourseOrderItem> courseOrderItems = courseOrderItemMapper.selectList(wrapper);
            courseOrder.setItems(courseOrderItems);
        }
        return courseOrder;
    }

    @Override
    public void closeOrder(PlaceCourseOrderTo placeCourseOrderTo) {
        synchronized (placeCourseOrderTo.getOrderNo()){
            CourseOrder order = courseOrderMapper.selectByOrderNo(placeCourseOrderTo.getOrderNo());
            //订单状态还处于支付中，那就是支付超时，关闭订单
            if(order != null && order.getStatusOrder() == BaseConstants.Pay.STATUS_APPLY){
                order.setStatusOrder(BaseConstants.Order.STATUS_OUT_TIME_CHANEL);
                courseOrderMapper.updateById(order);
                //如果是秒杀，需要退回库存
                KilledInfo killedInfo = placeCourseOrderTo.getKilledInfo();
                if(killedInfo != null){
                    log.info("秒杀订单超时处理{}",placeCourseOrderTo);
                    //超时了，Redis中还存在秒杀信息，说明还没去下单，退库存处理
                    RSemaphore semaphore = redissonClient.getSemaphore(BaseConstants.Kill.KEY_KILL_STORE + killedInfo.getKillCourseId().toString());
                    semaphore.addPermits(killedInfo.getKillCount());

                    //删除秒杀资格
                    redisTemplate.delete(BaseConstants.Kill.KEY_KILLED_INFO + killedInfo.getOrderNo()+":"+killedInfo.getUserId());

                    //删除秒杀记录，用户可以进行二次秒杀
                    redisTemplate.delete(BaseConstants.Kill.KEY_KILLLOG+killedInfo.getUserId()+":"+killedInfo.getKillCourseId());
                    log.info("秒杀订单超时，退回库存数 {} ,删除用户　{} 的秒杀记录 ",killedInfo.getKillCount() ,killedInfo.getUserId() );
                }
            }
        }
    }

    @Override
    public PageList selectPage(CourseOrderQuery query) {
        long count = courseOrderMapper.selectCount(query);
        if(count == 0)return new PageList();
        return new PageList(count,courseOrderMapper.selectList(query));
    }


}
